package com.ruoyi.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.system.dto.ContractCreateDTO;
import com.ruoyi.system.dto.ContractDTO;
import com.ruoyi.system.dto.ContractUpdateDTO;
import com.ruoyi.system.dto.ContractAuditDTO;
import com.ruoyi.system.dto.ProjectDTO;
import com.ruoyi.system.dto.convert.ContractMapStruct;
import com.ruoyi.system.dto.convert.ProjectConvert;
import com.ruoyi.system.entity.ContractDO;
import com.ruoyi.system.entity.ContractFile;
import com.ruoyi.system.domain.Project;
import com.ruoyi.system.mapper.ContractMapper;
import com.ruoyi.system.mapper.ProjectMapper;
import com.ruoyi.system.service.ContractService;
import com.ruoyi.common.utils.OSSUtils;
import com.ruoyi.system.service.ProjectService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Objects;
import java.util.Set;
import java.util.HashSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.math.BigDecimal;
import com.ruoyi.system.service.ContractPaymentScheduleService;
import com.ruoyi.system.dto.ContractPaymentScheduleDTO;

@Slf4j
@Service
@RequiredArgsConstructor
public class ContractServiceImpl extends ServiceImpl<ContractMapper, ContractDO> implements ContractService {

    @Resource
    private ContractFileServiceImpl contractFileService;

    @Resource
    private ProjectService projectService;

    @Resource
    private OSSUtils ossUtils;

    @Resource
    private ContractPaymentScheduleService paymentScheduleService;

    @Override
    public ContractDTO getById(Long id) {
        ContractDO contractDO = this.lambdaQuery()
                .eq(ContractDO::getId, id)
                .eq(ContractDO::getIsDeleted, 0)
                .one();
        if (contractDO == null) {
            return null;
        }
        ContractDTO dto = ContractMapStruct.INSTANCE.toDTO(contractDO);
        
        // 查询合同文件列表
        List<ContractFile> list = contractFileService.lambdaQuery()
                .eq(ContractFile::getContractId, id)
                .eq(ContractFile::getIsDelete, 0)
                .list();
        dto.setContractFiles(list);
        
        // 查询付款周期列表
        try {
            List<ContractPaymentScheduleDTO> paymentSchedules = paymentScheduleService.getByContractId(id);
            dto.setPaymentSchedules(paymentSchedules);
        } catch (Exception e) {
            log.warn("查询合同付款周期失败，合同ID: {}", id, e);
            dto.setPaymentSchedules(new ArrayList<>());
        }

        ProjectDTO project = projectService.getProjectById(contractDO.getProjectId());
        dto.setProjectName(project != null ? project.getProjectName() : null);
        dto.setCustomerId(project != null ? project.getH5UserId() : null);

        return dto;
    }

    @Override
    public List<ContractDTO> listAll() {
        List<ContractDO> contractDOList = baseMapper.selectList(null);
        return ContractMapStruct.INSTANCE.toDTOList(contractDOList);
    }

    @Override
    public ContractDTO create(ContractCreateDTO dto) {
        ContractDO contractDO = ContractMapStruct.INSTANCE.toDO(dto);
        baseMapper.insert(contractDO);
        return ContractMapStruct.INSTANCE.toDTO(contractDO);
    }

    /**
     * 核心方法：创建合同并处理文件上传
     * 事务注解确保：合同创建、文件上传、文件记录保存要么全部成功，要么全部回滚
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public ContractDTO createWithFiles(ContractCreateDTO dto) {
        // 1. 先创建合同主记录，获取合同ID
        ContractDO contract = ContractMapStruct.INSTANCE.toDO(dto);
        baseMapper.insert(contract);  // 插入数据库，生成自增ID
        Long contractId = contract.getId();  // 合同唯一标识

        if (!dto.getContractFiles().isEmpty()) {
            List<ContractFile> contractFiles = dto.getContractFiles();
            contractFiles.forEach(file -> file.setContractId(contractId));
            contractFileService.saveBatch(contractFiles);  // 批量保存附件记录
        }

        // 3. 创建默认付款周期
        if (contract.getSignedAmount() != null && contract.getSignedAmount().compareTo(BigDecimal.ZERO) > 0) {
            try {
                paymentScheduleService.createDefaultPaymentSchedules(contractId, contract.getSignedAmount());
                log.info("合同创建时自动生成默认付款周期，合同ID: {}, 合同金额: {}", contractId, contract.getSignedAmount());
            } catch (Exception e) {
                log.error("创建默认付款周期失败，合同ID: {}, 合同金额: {}", contractId, contract.getSignedAmount(), e);
                // 付款周期创建失败不影响合同创建，只记录日志
            }
        }

        // 4. 组装返回结果（含合同基本信息）
        ContractDTO result = new ContractDTO();
        BeanUtils.copyProperties(contract, result);
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ContractDTO update(ContractUpdateDTO dto) {

        ContractDO one = this.lambdaQuery()
                .eq(ContractDO::getId, dto.getId())
                .eq(ContractDO::getIsDeleted, 0)
                .one();
        if (one == null) {
            throw new ServiceException("合同不存在");
        }
        if (one.getAuditStatus() == 1) {
            throw new ServiceException("合同还在审核中，暂无法修改");
        }
        if (!dto.getContractFiles().isEmpty()) {
            List<ContractFile> contractFiles = dto.getContractFiles();
            contractFiles.forEach(file -> file.setContractId(dto.getId()));
            contractFileService.saveBatch(contractFiles);  // 批量保存附件记录
        }
        ContractDO contractDO = ContractMapStruct.INSTANCE.toDO(dto);
        baseMapper.updateById(contractDO);
        return ContractMapStruct.INSTANCE.toDTO(contractDO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteById(Long id) {

        ContractDO contractDO = this.lambdaQuery()
                .eq(ContractDO::getId, id)
                .eq(ContractDO::getIsDeleted, 0)
                .one();
        if (contractDO == null) {
            throw new ServiceException("合同不存在");
        }

        if (contractDO.getAuditStatus() == 1) {
            throw new ServiceException("合同还在审核中，暂无法删除");
        }
        baseMapper.deleteById(id);
        contractFileService.removeByContractId(id);
        return true;
    }

    @Override
    public List<ContractDTO> getByContractType(String contractType) {
        List<ContractDO> contractDOList = this.lambdaQuery()
                .eq(ContractDO::getContractType, contractType)
                .list();
        return ContractMapStruct.INSTANCE.toDTOList(contractDOList);
    }

    @Override
    public List<ContractDTO> listByCondition(
            String projectId,
            String projectName,
            String projectAddress,
            String stage,
            String contractSigner,
            String contractType,
            String contractName,
            Integer auditStatus,
            LocalDate signStartDate,
            LocalDate signEndDate) {
        // 0. 依据主体类型进行范围与状态约束
        Long currentUserId = SecurityUtils.getUserId();
        boolean isAdmin = SecurityUtils.hasRole("admin");

        // 管理员仅看待审核（强制覆盖外部传入）
//        Integer effectiveAuditStatus = isAdmin ? 1 : auditStatus;

        // 1. 构建 MyBatis-Plus 查询条件
        LambdaQueryWrapper<ContractDO> queryWrapper = new LambdaQueryWrapper<>();
        // 项目名称模糊匹配
//        if (projectName != null && !projectName.trim().isEmpty()) {
//            queryWrapper.like(ContractDO::getProjectName, projectName);
//        }
//        // 项目地址模糊匹配
//        if (projectAddress != null && !projectAddress.trim().isEmpty()) {
//            queryWrapper.like(ContractDO::getProjectAddress, projectAddress);
//        }
//        // 业务阶段精确匹配
//        if (stage != null && !stage.trim().isEmpty()) {
//            queryWrapper.eq(ContractDO::getStage, stage);
//        }
        // 项目ID精确匹配
        if (projectId != null && !projectId.trim().isEmpty()) {
            queryWrapper.eq(ContractDO::getProjectId, projectId);
        }
        // 合同签订人精确匹配
        if (contractSigner != null && !contractSigner.trim().isEmpty()) {
            queryWrapper.eq(ContractDO::getSigner, contractSigner);
        }
        // 合同类型精确匹配
        if (contractType != null && !contractType.trim().isEmpty()) {
            queryWrapper.eq(ContractDO::getContractType, contractType);
        }
        // 合同名称模糊匹配
        if (contractName != null && !contractName.trim().isEmpty()) {
            queryWrapper.like(ContractDO::getContractName, contractName);
        }
        // 签订时间范围匹配
        if (signStartDate != null) {
            queryWrapper.ge(ContractDO::getSignDate, signStartDate);
        }
        if (signEndDate != null) {
            queryWrapper.le(ContractDO::getSignDate, signEndDate);
        }
        // 审核状态精确匹配（管理员强制=待审核）
//        queryWrapper.eq(ContractDO::getAuditStatus, effectiveAuditStatus);

        // 设计师仅能查看自己项目的合同：project_id in (select id from t_project where designer_id = currentUserId)
        if (!isAdmin && currentUserId != null) {
            queryWrapper.inSql(ContractDO::getProjectId, "select id from t_project where designer_id = " + currentUserId);
        }
        // 未删除
        queryWrapper.eq(ContractDO::getIsDeleted, 0);

        // 2. 查询合同DO列表
        List<ContractDO> contractDOList = this.list(queryWrapper);

        // 3. 转换为DTO并关联文件
        return contractDOList.stream().map(contractDO -> {
            ContractDTO dto = ContractMapStruct.INSTANCE.toDTO(contractDO);
            List<ContractFile> files = contractFileService.lambdaQuery()
                    .eq(ContractFile::getContractId, contractDO.getId())
                    .eq(ContractFile::getIsDelete, 0)
                    .list();
            dto.setContractFiles(files);
            return dto;
        }).collect(Collectors.toList());
    }

    @Override
    public long countApprovedContractCustomers() {
        List<ContractDO> approvedContracts = this.lambdaQuery()
                .eq(ContractDO::getAuditStatus, 2)  // 审核通过
                .eq(ContractDO::getIsDeleted, 0)    // 未删除
                .list();

        return approvedContracts.stream()
                .map(ContractDO::getProjectId)
                .filter(Objects::nonNull)  // 过滤空值
                .distinct()  // 去重
                .count();
    }

    @Override
    public Map<String, Integer> approvedSignedProjectsDaily(LocalDate month) {
        LocalDate base = month != null ? month : LocalDate.now();
        LocalDate monthStart = base.withDayOfMonth(1);
        LocalDate monthEnd = monthStart.plusMonths(1).minusDays(1);

        List<ContractDO> contracts = this.lambdaQuery()
                .eq(ContractDO::getAuditStatus, 2)
                .eq(ContractDO::getIsDeleted, 0)
                .ge(ContractDO::getSignDate, monthStart)
                .le(ContractDO::getSignDate, monthEnd)
                .select(ContractDO::getProjectId, ContractDO::getSignDate)
                .list();

        LinkedHashMap<String, Integer> summary = new LinkedHashMap<>();
        LocalDate cursor = monthStart;
        DateTimeFormatter fmt = DateTimeFormatter.ISO_LOCAL_DATE;
        while (!cursor.isAfter(monthEnd)) {
            summary.put(cursor.format(fmt), 0);
            cursor = cursor.plusDays(1);
        }

        Map<String, Long> dailyDistinctProjects = contracts.stream()
                .filter(c -> c.getSignDate() != null && c.getProjectId() != null)
                .collect(Collectors.groupingBy(c -> c.getSignDate().format(fmt), LinkedHashMap::new,
                        Collectors.mapping(ContractDO::getProjectId, Collectors.collectingAndThen(Collectors.toSet(), set -> (long) set.size()))));

        dailyDistinctProjects.forEach((k, v) -> summary.computeIfPresent(k, (kk, vv) -> v.intValue()));
        return summary;
    }

    @Override
    public List<ProjectDTO> approvedSignedProjectsByDate(LocalDate date) {
        LocalDate target = date != null ? date : LocalDate.now();
        List<ContractDO> contracts = this.lambdaQuery()
                .eq(ContractDO::getAuditStatus, 2)
                .eq(ContractDO::getIsDeleted, 0)
                .eq(ContractDO::getSignDate, target)
                .select(ContractDO::getProjectId)
                .list();
        java.util.Set<Long> projectIds = contracts.stream()
                .map(ContractDO::getProjectId)
                .filter(java.util.Objects::nonNull)
                .collect(java.util.stream.Collectors.toSet());
        if (projectIds.isEmpty()) {
            return java.util.Collections.emptyList();
        }
        List<Project> projects = projectService.listByIds(projectIds);
        return projects.stream()
                .filter(p -> p.getDeleted() != null && p.getDeleted() == 0)
                .map(ProjectConvert.INSTANCE::convert)
                .collect(java.util.stream.Collectors.toList());
    }

    @Override
    public void downloadContractAttachmentsAsZip(Long contractId, HttpServletResponse response) {
        log.info("开始下载合同附件压缩包，合同ID: {}", contractId);
        
        // 1. 验证合同是否存在
        ContractDO contract = this.lambdaQuery()
                .eq(ContractDO::getId, contractId)
                .eq(ContractDO::getIsDeleted, 0)
                .one();
        
        if (contract == null) {
            throw new ServiceException("合同不存在");
        }
        
        // 2. 获取合同的所有附件
        List<ContractFile> contractFiles = contractFileService.lambdaQuery()
                .eq(ContractFile::getContractId, contractId)
                .eq(ContractFile::getIsDelete, 0)
                .list();
        
        if (contractFiles.isEmpty()) {
            throw new ServiceException("该合同没有附件");
        }
        
        // 3. 设置响应头
        String contractName = contract.getContractName() != null ? contract.getContractName() : "未知合同";
        String zipFileName = String.format("合同附件_%s_%s.zip", 
                contractName, 
                LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
        
        try {
            // 处理中文文件名编码
            String encodedFileName = URLEncoder.encode(zipFileName, StandardCharsets.UTF_8.toString())
                    .replaceAll("\\+", "%20");
            
            response.setContentType("application/zip");
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
            response.setHeader("Cache-Control", "no-cache");
            
            // 4. 创建ZIP输出流
            try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream(), StandardCharsets.UTF_8)) {
                
                // 用于跟踪已添加的文件名，避免重复
                Set<String> addedFileNames = new HashSet<>();
                
                for (ContractFile contractFile : contractFiles) {
                    InputStream fileInputStream = null;
                    try {
                        // 从OSS下载文件
                        fileInputStream = ossUtils.download("contract/" + contractFile.getFileName());
                        
                        // 创建ZIP条目
                        String entryName = contractFile.getOriginalName();
                        // 处理文件名重复的情况
                        entryName = handleDuplicateFileName(entryName, addedFileNames);
                        
                        ZipEntry zipEntry = new ZipEntry(entryName);
                        zipEntry.setSize(contractFile.getFileSize());
                        zipOut.putNextEntry(zipEntry);
                        
                        // 将文件内容写入ZIP
                        byte[] buffer = new byte[8192];
                        int bytesRead;
                        while ((bytesRead = fileInputStream.read(buffer)) != -1) {
                            zipOut.write(buffer, 0, bytesRead);
                        }
                        
                        zipOut.closeEntry();
                        
                        // 记录已添加的文件名
                        addedFileNames.add(entryName);
                        
                        log.info("成功添加文件到压缩包: {}", entryName);
                        
                    } catch (Exception e) {
                        log.error("处理文件失败: {}, 错误: {}", contractFile.getOriginalName(), e.getMessage());
                        // 继续处理其他文件，不中断整个流程
                    } finally {
                        // 确保输入流被关闭
                        if (fileInputStream != null) {
                            try {
                                fileInputStream.close();
                            } catch (IOException e) {
                                log.warn("关闭文件输入流失败: {}", e.getMessage());
                            }
                        }
                    }
                }
                
                zipOut.finish();
                log.info("合同附件压缩包下载完成，合同ID: {}, 文件数量: {}", contractId, contractFiles.size());
                
            } catch (IOException e) {
                log.error("创建ZIP文件失败", e);
                throw new ServiceException("创建压缩包失败: " + e.getMessage());
            }
            
        } catch (UnsupportedEncodingException e) {
            log.error("文件名编码失败", e);
            throw new ServiceException("文件名编码失败: " + e.getMessage());
        }
    }
    
    /**
     * 处理ZIP中重复的文件名
     * @param fileName 原始文件名
     * @param addedFileNames 已添加的文件名集合
     * @return 处理后的文件名
     */
    private String handleDuplicateFileName(String fileName, Set<String> addedFileNames) {
        String baseName = fileName;
        String extension = "";
        
        // 分离文件名和扩展名
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex > 0) {
            baseName = fileName.substring(0, dotIndex);
            extension = fileName.substring(dotIndex);
        }
        
        String finalFileName = fileName;
        int counter = 1;
        
        // 检查是否已存在同名文件
        while (addedFileNames.contains(finalFileName)) {
            finalFileName = baseName + "_" + counter + extension;
            counter++;
        }
        
        return finalFileName;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approveContract(ContractAuditDTO auditDTO) {
        log.info("开始审核通过合同，合同ID: {}, 审核人: {}", auditDTO.getContractId(), auditDTO.getAuditorName());
        
        // 1. 验证合同是否存在
        ContractDO contract = this.lambdaQuery()
                .eq(ContractDO::getId, auditDTO.getContractId())
                .eq(ContractDO::getIsDeleted, 0)
                .one();
        
        if (contract == null) {
            throw new ServiceException("合同不存在");
        }
        
        // 2. 验证合同当前状态是否允许审核
        if (contract.getAuditStatus() == null) {
            throw new ServiceException("合同状态异常，无法审核");
        }
        
        if (contract.getAuditStatus() == 2) {
            throw new ServiceException("合同已审核通过，无需重复审核");
        }
        
        if (contract.getAuditStatus() == 3) {
            throw new ServiceException("合同已审核拒绝，请先修改合同内容后再提交审核");
        }
        
        // 3. 更新合同审核状态为通过
        ContractDO updateContract = new ContractDO();
        updateContract.setId(auditDTO.getContractId());
        updateContract.setAuditStatus(2); // 2-审核通过
        updateContract.setUpdateTime(LocalDateTime.now());
        
        boolean updateResult = this.updateById(updateContract);
        if (!updateResult) {
            throw new ServiceException("合同审核状态更新失败");
        }
        
        log.info("合同审核通过成功，合同ID: {}, 合同名称: {}, 审核人: {}, 审核意见: {}", 
                auditDTO.getContractId(), contract.getContractName(), 
                auditDTO.getAuditorName(), auditDTO.getAuditComment());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rejectContract(ContractAuditDTO auditDTO) {
        log.info("开始审核拒绝合同，合同ID: {}, 审核人: {}", auditDTO.getContractId(), auditDTO.getAuditorName());
        
        // 1. 验证合同是否存在
        ContractDO contract = this.lambdaQuery()
                .eq(ContractDO::getId, auditDTO.getContractId())
                .eq(ContractDO::getIsDeleted, 0)
                .one();
        
        if (contract == null) {
            throw new ServiceException("合同不存在");
        }
        
        // 2. 验证合同当前状态是否允许审核
        if (contract.getAuditStatus() == null) {
            throw new ServiceException("合同状态异常，无法审核");
        }
        
        if (contract.getAuditStatus() == 2) {
            throw new ServiceException("合同已审核通过，无法拒绝");
        }
        
        if (contract.getAuditStatus() == 3) {
            throw new ServiceException("合同已审核拒绝，无需重复操作");
        }
        
        // 3. 验证审核意见
        if (auditDTO.getAuditComment() == null || auditDTO.getAuditComment().trim().isEmpty()) {
            throw new ServiceException("审核拒绝时必须填写拒绝原因");
        }
        
        // 4. 更新合同审核状态为拒绝
        ContractDO updateContract = new ContractDO();
        updateContract.setId(auditDTO.getContractId());
        updateContract.setAuditStatus(3); // 3-审核驳回
        updateContract.setUpdateTime(LocalDateTime.now());
        
        // 如果有审核意见，可以存储到备注字段中
        if (auditDTO.getAuditComment() != null && !auditDTO.getAuditComment().trim().isEmpty()) {
            String auditInfo = String.format("[审核拒绝] %s - %s: %s", 
                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
                    auditDTO.getAuditorName(), 
                    auditDTO.getAuditComment());
            
            // 将审核信息追加到现有备注中
            String existingRemarks = contract.getRemarks();
            String newRemarks = existingRemarks != null ? existingRemarks + "\n" + auditInfo : auditInfo;
            updateContract.setRemarks(newRemarks);
        }
        
        boolean updateResult = this.updateById(updateContract);
        if (!updateResult) {
            throw new ServiceException("合同审核状态更新失败");
        }
        
        log.info("合同审核拒绝成功，合同ID: {}, 合同名称: {}, 审核人: {}, 拒绝原因: {}", 
                auditDTO.getContractId(), contract.getContractName(), 
                auditDTO.getAuditorName(), auditDTO.getAuditComment());
    }
}

